home *** CD-ROM | disk | FTP | other *** search
/ C/C++ Users Group Library 1997 August / Walnut Creek CDROM.7z / LISTINGS / V_13_04 / CHAPMAN2 / CHAPMAN2.ZIP / ERRMGR.CPP < prev    next >
Encoding:
C/C++ Source or Header  |  1994-03-02  |  20.3 KB  |  583 lines

  1. /* errmgr.cpp - error manager */
  2.  
  3. /* see errmgr.hpp for details of the definition of this module. */
  4.  
  5. #include <stdio.h>                      /* various and sundry, esp. stderr */
  6. #include <ctype.h>                      /* toupper() */
  7. #include <stdlib.h>                     /* exit() */
  8. #include <stdarg.h>                     /* varargs */
  9. #include <string.h>
  10. #include "complain.hpp"                 /* complaint dictionary interface */
  11. #include "utils.hpp"
  12. #include "c_failur.hpp"                 /* counting_failure_handler */
  13. #include "errmgr.hpp"                   /* us */
  14.  
  15. /* an element in the list of complaint dictionaries: */
  16.  
  17. class error_dict_list {                 /* LIFO list (not stack; no pop) */
  18.     public:
  19.         error_dict_list(error_dict_list *curr,complaint_dict *new_dict);
  20.         error_dict_list *prev;
  21.         complaint_dict *dict;
  22. };  /* end of class error_dict_list */
  23.  
  24.  
  25. error_mgr err_mgr;                      /* declaration, not definition */
  26.  
  27. /* is_set_up is set when err_mgr is initialized. no other error manager can */
  28. /* be active. */
  29.  
  30. int error_mgr::is_set_up = 0;
  31.  
  32. /* asserts_off is 0 when assertions are to be checked. it is set */
  33. /* whenever set_assert_flag() is called with a 0 parameter. */
  34.  
  35. /* NDEBUG controls only the default value of the assertions flag, as */
  36. /* opposed to whether assertions are run or not. if NDEBUG is defined */
  37. /* then assertions must be requested specifically by the application. */
  38.  
  39. #ifndef NDEBUG
  40. int error_mgr::asserts_off = 0;
  41. #else
  42. int error_mgr::asserts_off = 1;
  43. #endif
  44.  
  45. /* all of these class static variables are set to 0 because we cannot */
  46. /* ensure that errors will not be reported in some constructor before */
  47. /* err_mgr is constructed. only assignments to 0 are guaranteed to be */
  48. /* performed before any constructor is called. */
  49.  
  50. failure_handler *error_mgr::curr_handler = 0,
  51.                 *error_mgr::default_handler = 0;
  52. error_dict_list *error_mgr::error_dicts = 0;
  53. ptr_stack *error_mgr::handler_stack = 0;
  54.  
  55. /* this demonstration implementation assumes that the buffer won't */
  56. /* overflow; it really should use a managed buffer that grows to fit the */
  57. /* message text. */
  58.  
  59. static char line[512];
  60.  
  61. /* compares two printf() format strings and returns 1 if they are */
  62. /* functionally equivalent (all format specifiers in the same order). */
  63.  
  64. static int proper_format(const char *pgm_text,const char *user_text);
  65.  
  66. /* finds the next printf() format specifier and returns a unique character */
  67. /* identifying its type. advances *fmt to point past the specifier. */
  68.  
  69. static char format_specifier(const char **fmt);
  70.  
  71. error_mgr::error_mgr(void)              /* constructor */
  72. {
  73.     /* it's possible that an error in a constructor may cause code */
  74.     /* to be invoked before the constructor is run. as a result */
  75.     /* everything calls setup() first. we call it here just in case */
  76.     /* it hasn't been done yet. */
  77.  
  78.     setup();
  79.  
  80. }  /* end of error_mgr::error_mgr() */
  81.  
  82. error_mgr::~error_mgr(void)             /* destructor */
  83. {
  84.     /* no point in creating more "memory leaks" to sign off on. */
  85.  
  86.     delete default_handler;
  87.     delete handler_stack;
  88.  
  89. }  /* end of error_mgr::~error_mgr() */
  90.  
  91. void error_mgr::setup(void)
  92. {
  93.     /* setup: create a default handler and an empty dictionary list. */
  94.     /* also allocate a pointer stack for failure_handlers. make sure */
  95.     /* that no other error_mgr is ever defined. */
  96.  
  97.     /* this routine gets called by the "working" routines to ensure that */
  98.     /* everything is functional should an error message be called by a */
  99.     /* constructor before this module would be initialized. we only know */
  100.     /* that initializations to 0 are guaranteed. */
  101.  
  102.     if (this != &err_mgr)               /* just in case... */
  103.         err_mgr.fail("Duplicate error_mgr was defined.\n");
  104.     if (is_set_up)
  105.         return;
  106.     is_set_up = 1;
  107.     curr_handler = default_handler = new failure_handler;
  108.     handler_stack = new ptr_stack;
  109.  
  110. }  /* end of error_mgr::setup() */
  111.  
  112. int error_mgr::define_dictionary(const char *filename)
  113. {
  114.     /* error dictionary management - add a new file to the list of key */
  115.     /* lookups. returns 0 if any errors are detected; the dictionary */
  116.     /* is invalid and will not be referenced. */
  117.  
  118.     counting_failure_handler *our_handler;
  119.     complaint_dict *new_dict;
  120.  
  121.     /* in reality, the dictionary may in fact have some valid definitions */
  122.     /* in it, but I've drawn a hard line here and declared that it will */
  123.     /* not be used. */
  124.  
  125.     /* note the use of the counting handler to tally errors in the file. */
  126.     /* this gets us around the "no constructor return value" problem. */
  127.  
  128.     setup();
  129.     our_handler = new counting_failure_handler;  /* inserts automatically */
  130.     new_dict = new complaint_dict(filename);
  131.     if (our_handler->errors_logged() || our_handler->warns_logged()) {
  132.         delete new_dict;
  133.         warn("$bad_error_dict: dictionary file \"%s\" has "
  134.              "errors - will not be used\n",filename);
  135.         delete our_handler;             /* removes self automatically */
  136.         return 0;
  137.     }
  138.  
  139.     /* everything looks good - install the dictionary in the chain. */
  140.  
  141.     error_dicts = new error_dict_list(error_dicts,new_dict);
  142.     delete our_handler;
  143.     return 1;
  144.  
  145. }  /* end of error_mgr::define_dictionary() */
  146.  
  147. failure_handler *error_mgr::define_handler(failure_handler *new_handler)
  148. {
  149.     /* install the argument as the new error handler, pushing the previous */
  150.     /* handler onto a stack. a GUI, for example, would supply a window-based */
  151.     /* handler upon startup and return to the previous handler on exit (in */
  152.     /* case there are any errors after the GUI has shut down). */
  153.  
  154.     failure_handler *old_handler;
  155.  
  156.     setup();
  157.     handler_stack->push(curr_handler);
  158.     old_handler = curr_handler;
  159.     if (new_handler == 0)               /* must be bomb-proof */
  160.         curr_handler = default_handler;
  161.     else
  162.         curr_handler = new_handler;
  163.     return old_handler;
  164.  
  165. }  /* end of error_mgr::define_handler() */
  166.  
  167. failure_handler *error_mgr::restore_handler(void)
  168. {
  169.     /* return to the previous error handler, falling back to the default */
  170.     /* handler should the user go too far in popping handlers. */
  171.  
  172.     failure_handler *old_handler;
  173.  
  174.     setup();
  175.     old_handler = curr_handler;
  176.     curr_handler = (failure_handler *) (handler_stack->pop());
  177.     if (curr_handler == 0)
  178.         curr_handler = default_handler;
  179.     return old_handler;
  180.  
  181. }  /* end of error_mgr::restore_handler() */
  182.  
  183. const char *error_mgr::message(const char *fmt,char *msg_line,
  184.                                int linelen)
  185. {
  186.     /* the format of an error message being passed to fail(), vfail(), etc. */
  187.     /* [ '$' <key> ':' ] <default message>. if the first character of the */
  188.     /* message is '$' then split off the key and look it up. if it's found */
  189.     /* and if it has the proper format, return the text defined for the */
  190.     /* key instead. otherwise, prepend a warning to the default message */
  191.     /* and print the default. thus, we get a response out even if no */
  192.     /* dictionaries are defined or if there is an error in the dictionary */
  193.     /* containing the key. */
  194.  
  195.     /* interface: this function can also be used to return a string value */
  196.     /* for later insertion into printed text. */
  197.  
  198.     /* the message text is written into msg_line, which is returned so that */
  199.     /* a call to this function can be used in printf(). */
  200.  
  201.     const char *new_fmt,*key,*keystart;
  202.     char *key_alloc,*buf;
  203.     int keylen;
  204.  
  205.     /* since fail(), vfail(), error(), verror(), warn(), vwarn(), post(), */
  206.     /* and vpost() all call message() first, this routine calls setup() */
  207.     /* for them. */
  208.  
  209.     setup();
  210.     ASSERT(fmt != msg_line);            /* can't write into fmt! */
  211.     msg_line[0] = '\0';
  212.     if (*fmt != '$') {                  /* not a keyed message? */
  213.         strncpy(msg_line,fmt,linelen);
  214.         msg_line[linelen - 1] = '\0';   /* in case strncpy() didn't */
  215.         return msg_line;
  216.     }
  217.  
  218.     /* extract the key, which must be alphanumeric (just like in an error */
  219.     /* dictionary). note that fmt is advanced past the '$'; we leave it */
  220.     /* there in case the replacement text is ill-formed so that the user */
  221.     /* will know which text to fix. */
  222.  
  223.     key = ++fmt;                        /* skip '$' */
  224.     key += skipblanks(key);             /* skip leading blanks */
  225.     keylen = skip_ident(key);           /* fetch key name */
  226.     new_fmt = key + keylen;             /* skip key name */
  227.     new_fmt += skipblanks(key);         /* skip to ':' */
  228.  
  229.     /* if the format string is malformed, prepend a nasty message and pass */
  230.     /* it on. here we assume it will fit; this should be writing into a */
  231.     /* managed buffer. */
  232.  
  233.     /* note that no error within the error system (errmgr.cpp or */
  234.     /* complain.cpp) is replaceable: a recursive call, with the potential */
  235.     /* for other errors, would be dangerous. */
  236.  
  237.     if (keylen == 0 || *new_fmt != ':') {  /* malformed format string! */
  238.         sprintf(msg_line,"Keyed error message format string is malformed\n%s",
  239.                 fmt);
  240.     }
  241.     else {
  242.         key_alloc = newstring(key,keylen);
  243.         ++new_fmt;                      /* skip ':' */
  244.         new_fmt += skipblanks(new_fmt);
  245.         if (find_replacement(key_alloc,msg_line,linelen)) {
  246.             if (!proper_format(new_fmt,msg_line)) {
  247.  
  248.                 /* we leave the dictionary text at the front of the */
  249.                 /* message. */
  250.  
  251.                 buf = msg_line + strlen(msg_line);
  252.                 sprintf(buf,"Replacement text for keyed error "
  253.                         "message is malformed\n%s",fmt);
  254.             }
  255.         }  /* end of if(replacement text found) */
  256.  
  257.         /* the text wasn't found - use the program version. */
  258.  
  259.         else {
  260.             strncpy(msg_line,new_fmt,linelen);
  261.             msg_line[linelen - 1] = '\0';  /* in case strncpy() didn't */
  262.         }
  263.         free(key_alloc);
  264.     }  /* end of else(!malformed) */
  265.  
  266.     return msg_line;
  267.  
  268. }  /* end of error_mgr::message() */
  269.  
  270. int error_mgr::find_replacement(const char *key,char *msg_line,int linelen)
  271. {
  272.     /* if there is a replacement for the message indexed by key, then */
  273.     /* put it into the buffer. return 1 if found, 0 if not. */
  274.  
  275.     error_dict_list *curr_dict;
  276.  
  277.     for (curr_dict = error_dicts; curr_dict != 0;
  278.          curr_dict = curr_dict->prev)
  279.         if (curr_dict->dict->key_defined(key))
  280.             break;
  281.     return curr_dict != 0 &&
  282.            curr_dict->dict->complaint_text(key,msg_line,linelen);
  283.  
  284. }  /* end of error_mgr::find_replacement() */
  285.  
  286. static int proper_format(const char *pgm_text,const char *user_text)
  287. {
  288.     /* return 1 if the printf() format specifiers in user_text are the */
  289.     /* same (and in the same order) as the ones in pgm_text. */
  290.  
  291.     char pgm_fmt,user_fmt;
  292.  
  293.     do {
  294.  
  295.         /* get the next format character (i.e. the 'd' from "%d") from each */
  296.         /* string and compare them. we use a canonical form. */
  297.  
  298.         pgm_fmt = format_specifier(&pgm_text);
  299.         user_fmt = format_specifier(&user_text);
  300.         if (pgm_fmt != user_fmt)
  301.             return 0;
  302.     } while (*pgm_text || *user_text);  /* can't have extras anywhere! */
  303.  
  304.     return 1;
  305.  
  306. }  /* end of proper_format() */
  307.  
  308. static char format_specifier(const char **fmt)
  309. {
  310.     /* skip to the next '%' specifier (if any) and return a unique letter */
  311.     /* indicating what type it is. some specifiers get mapped to a */
  312.     /* canonical form: "ld" is mapped to 'l'; 'e', 'f', and 'g' are mapped */
  313.     /* to 'f', etc. */
  314.  
  315.     /* returns '\0' when **fmt runs out. advances *fmt past the specifier. */
  316.  
  317.     const char *s = *fmt;               /* easier to use *s than **fmt */
  318.     char c;
  319.  
  320.     /* first, look for a format specifier. */
  321.  
  322.     for (;;) {                          /* exit from within */
  323.         while (*s && *s != '%')         /* skip to '%' */
  324.             ++s;
  325.         if (*s == '\0')                 /* off end? */
  326.             break;
  327.         ++s;                            /* skip '%' */
  328.         if (*s == '%')                  /* "%%" prints '%' */
  329.             ++s;                        /* not a format specifier */
  330.         else
  331.             break;                      /* anything else - assume specifier */
  332.     }  /* end of for(ever) */
  333.  
  334.     if (*s == '\0') {                   /* fell off end? */
  335.         *fmt = s;                       /* update caller's variable */
  336.         return '\0';
  337.     }
  338.  
  339.     /* skip all of the auxiliary junk until we get to the specifying */
  340.     /* letter. we don't test the validity of the junk; to do so would */
  341.     /* almost duplicate the functionality of printf(). */
  342.  
  343.     while (*s && !isspace(*s) && !isalpha(*s))
  344.         ++s;
  345.     if (!isalpha(*s)) {
  346.         *fmt = s;                       /* update caller's variable */
  347.         return '\0';                    /* bad format specifier */
  348.     }
  349.  
  350.     /* if we find an 'l' we assume an integer specification follows */
  351.     /* and return an 'l'. what's important is that a long integer */
  352.     /* is to be pulled off the stack when printf() gets here. */
  353.  
  354.     if (*s == 'l' && isalpha(*(s + 1))) {  /* long (integer assumed) */
  355.         *fmt = s + 2;               /* skip 'l' and specifier */
  356.         return 'l';                 /* something done with a long */
  357.     }
  358.  
  359.     /* the format specifier is probably OK (though we don't examine it */
  360.     /* more closely). map it into something canonical and return it. */
  361.  
  362.     c = tolower(*s);
  363.     *fmt = s + 1;                       /* update caller's variable */
  364.     switch(c) {
  365.     case 'e':                           /* floating point specifiers */
  366.     case 'f':                           /* a double is pulled off stack */
  367.     case 'g':
  368.         return 'e';
  369.     case 'o':                           /* integer specifiers */
  370.     case 'u':
  371.     case 'x':
  372.     case 'd':
  373.     case 'i':
  374.     case 'b':
  375.         return 'd';
  376.     default:                            /* all others - map into themselves */
  377.         return c;
  378.     }  /* end of switch(c) */
  379.  
  380.     /* NOTREACHED */
  381.  
  382. }  /* end of format_specifier() */
  383.  
  384. void error_mgr::fail(const char *fmt,...)
  385. {
  386.     /* something horrible has happened. print the message and exit. note */
  387.     /* that the handler's fail() routine should exit, but in case it */
  388.     /* doesn't we enforce the decision. a failure is taken seriously. */
  389.     /* replace the format text if it's keyed. */
  390.  
  391.     va_list ap;
  392.  
  393.     va_start(ap,fmt);
  394.     vfail(fmt,ap);                      /* pass it on - won't return */
  395.  
  396.     /* NOTREACHED */
  397.  
  398.     va_end(ap);
  399.  
  400. }  /* end of error_mgr::fail() */
  401.  
  402. void error_mgr::vfail(const char *fmt,va_list ap)
  403. {
  404.     /* fail() with a va_list already built. see its description above. */
  405.  
  406.     fmt = message(fmt,line,sizeof(line));  /* get replacement text (if any) */
  407.     curr_handler->fail(fmt,ap);
  408.     exit(EXIT_FAILURE);                 /* just in case handler didn't! */
  409.  
  410. }  /* end of error_mgr::vfail() */
  411.  
  412. void error_mgr::error(const char *fmt,...)
  413. {
  414.     /* print the message and ask for permission to continue. this is all */
  415.     /* the responsibility of the handler, of course. replace the format */
  416.     /* text if it's keyed. */
  417.  
  418.     va_list ap;
  419.  
  420.     va_start(ap,fmt);
  421.     verror(fmt,ap);                     /* pass it on */
  422.     va_end(ap);
  423.  
  424. }  /* end of error_mgr::error() */
  425.  
  426. void error_mgr::verror(const char *fmt,va_list ap)
  427. {
  428.     /* error() with a va_list already built. see its description above. */
  429.  
  430.     fmt = message(fmt,line,sizeof(line));  /* get replacement text (if any) */
  431.     curr_handler->error(fmt,ap);
  432.  
  433. }  /* end of error_mgr::verror() */
  434.  
  435. void error_mgr::warn(const char *fmt,...)
  436. {
  437.     /* just have the handler print the message. replace the format text if */
  438.     /* it's keyed. */
  439.  
  440.     va_list ap;
  441.  
  442.     va_start(ap,fmt);
  443.     vwarn(fmt,ap);                      /* pass it on */
  444.     va_end(ap);
  445.  
  446. }  /* end of error_mgr::warn() */
  447.  
  448. void error_mgr::vwarn(const char *fmt,va_list ap)
  449. {
  450.     /* warn() with a va_list already built. see its description above. */
  451.  
  452.     fmt = message(fmt,line,sizeof(line));  /* get replacement text (if any) */
  453.     curr_handler->warn(fmt,ap);
  454.  
  455. }  /* end of error_mgr::vwarn() */
  456.  
  457. void error_mgr::post(const char *fmt,...)
  458. {
  459.     /* just have the handler print the message. replace the format text if */
  460.     /* it's keyed. */
  461.  
  462.     va_list ap;
  463.  
  464.     va_start(ap,fmt);
  465.     vpost(fmt,ap);                      /* pass it on */
  466.     va_end(ap);
  467.  
  468. }  /* end of error_mgr::post() */
  469.  
  470. void error_mgr::vpost(const char *fmt,va_list ap)
  471. {
  472.     /* post() with a va_list already built. see its description above. */
  473.  
  474.     fmt = message(fmt,line,sizeof(line));  /* get replacement text (if any) */
  475.     curr_handler->post(fmt,ap);
  476.  
  477. }  /* end of error_mgr::vpost() */
  478.  
  479. int error_mgr::set_assert_flag(int asserts_on)
  480. {
  481.     /* enable or disable assertions at run time. returns the previous */
  482.     /* state of the assertion flag. note that the internal flag has the */
  483.     /* opposite state since we want to use it to short-circuit assertion */
  484.     /* evaluation. */
  485.  
  486.     int retval = asserts_off;
  487.  
  488.     asserts_off = !asserts_on;
  489.     return !retval;
  490.  
  491. }  /* end of error_mgr::set_assert_flag() */
  492.  
  493. int error_mgr::assert_failed(const char *exp,const char *fname,
  494.                              unsigned linenum)
  495. {
  496.     /* an assertion has failed. print the file name, the line number, */
  497.     /* and the expression. error() allows the user to continue; for */
  498.     /* now I think this is a good idea. I may later change this to call */
  499.     /* fail() instead. */
  500.  
  501.     error("Assertion failed - file %s, line %u:\n%s\n",fname,linenum,exp);
  502.     return 1;                           /* an arbitrary value (needed for */
  503.                                         /* the macro definition) */
  504. }  /* end of error_mgr::assert_failed() */
  505.  
  506.  
  507. /* this is the default handler. its routines just send the message to */
  508. /* stderr. of course, to override these, inherit from the failure_handler */
  509. /* class and supply new ones. remember to assign the new handler using */
  510. /* define_handler()! */
  511.  
  512. static int ok_to_continue(void);
  513.  
  514. void failure_handler::fail(const char *fmt,va_list ap)
  515. {
  516.     /* a fatal error with arguments to be formatted. */
  517.  
  518.     fputs("FATAL: ",stderr);
  519.     vfprintf(stderr,fmt,ap);
  520.     exit(EXIT_FAILURE);                 /* that was all she wrote... */
  521.  
  522. }  /* end of failure_handler::fail() */
  523.  
  524. void failure_handler::error(const char *fmt,va_list ap)
  525. {
  526.     /* a serious error with arguments to be formatted. ask for permission */
  527.     /* to continue. */
  528.  
  529.     fputs("ERROR: ",stderr);
  530.     vfprintf(stderr,fmt,ap);
  531.     if (!ok_to_continue())
  532.         exit(EXIT_FAILURE);
  533.  
  534. }  /* end of failure_handler::error() */
  535.  
  536. void failure_handler::warn(const char *fmt,va_list ap)
  537. {
  538.     /* a warning with arguments to be formatted */
  539.  
  540.     fputs("WARNING: ",stderr);
  541.     vfprintf(stderr,fmt,ap);
  542.  
  543. }  /* end of failure_handler::warn() */
  544.  
  545. void failure_handler::post(const char *fmt,va_list ap)
  546. {
  547.     /* a message with arguments to be formatted. goes to stdout by */
  548.     /* default, unlike the other routines. */
  549.  
  550.     vfprintf(stdout,fmt,ap);            /* no prefix text */
  551.  
  552. }  /* end of failure_handler::post() */
  553.  
  554. static int ok_to_continue(void)
  555. {
  556.     /* request permission to continue. barring knowledge that I can read */
  557.     /* from stderr as well as write to it, I read from stdin. cave canem. */
  558.  
  559.     /* real cheap hack: I only look at the first character entered. if */
  560.     /* it's a 'y' or 'Y', then it's OK. otherwise we bomb. don't forget, */
  561.     /* this is the default behavior. if you want something better, then */
  562.     /* define your own class! */
  563.  
  564.     char buf[80];
  565.  
  566.     fputs("OK to continue [no]? ",stderr);
  567.     fgets(buf,sizeof(buf),stdin);
  568.     return toupper(buf[0]) == 'Y';
  569.  
  570. }  /* end of ok_to_continue() */
  571.  
  572. /* the constructor for the dictionary list - just links in the previous */
  573. /* head of the list. use: */
  574. /* error_dicts = new error_dict_list(error_dicts,new_dict); */
  575.  
  576. error_dict_list::error_dict_list(error_dict_list *curr,
  577.                                  complaint_dict *new_dict)
  578. {
  579.     prev = curr;                        /* link ourselves in */
  580.     dict = new_dict;
  581.  
  582. }  /* end of error_dict_list::error_dict_list() */
  583.